Import("env")
import pdb
import SCons.Util, SCons.Subst
import SconsUtils.utils as utils
import re
import subprocess

nenv = env.Clone()

nenv.Append(CFLAGS = "-include lib/config.h -Ilibraptor/ -DBUILDING_DLL -fvisibility=hidden ")

env.liboo_files = """
#lib/class.c #lib/talloc.c
#lib/error.c
"""

source_files = """
#lib/zip.c #lib/stringio.c #lib/resolver.c
#lib/rdf.c  #lib/image.c #lib/map.c
#lib/encode.c #lib/queue.c
#lib/graph.c #lib/data_store.c
#libreplace/replace.c
#lib/public.c #lib/misc.c
"""

source_files = """
#lib/stringio.c #lib/resolver.c
#lib/rdf.c #lib/file.c #lib/aff4_zip.c
#lib/encode.c #lib/queue.c
#lib/data_store.c #lib/aff4_image.c
#lib/aff4_utils.c
#libreplace/replace.c
#lib/public.c #lib/misc.c
"""


## Optional support for EWF and AFF1 volumes
#if utils.HEADERS.get("HAVE_LIBEWF_H"):
#    source_files += "  #lib/ewfvolume.c "

#if utils.HEADERS.get("HAVE_OPENSSL"):
#    source_files += "  #lib/encrypt.c "

uuid_files = """
#uuid/clear.c    #uuid/copy.c      #uuid/gen_uuid_nt.c  #uuid/pack.c
#uuid/unparse.c
#uuid/compare.c  #uuid/gen_uuid.c  #uuid/isnull.c       #uuid/parse.c
#uuid/unpack.c   #uuid/uuid_time.c
"""

## We compile these statically into our library to ensure users dont
## need tdb as a dependency and also to ensure we can port tdb for
## windows.
tdb_files = """
#libtdb/dump.c   #libtdb/freelist.c       #libtdb/io.c    #libtdb/open.c
#libtdb/transaction.c
#libtdb/error.c  #libtdb/freelistcheck.c  #libtdb/lock.c  #libtdb/tdb.c
#libtdb/traverse.c
"""

nenv.Append(CPPFLAGS = ['-Ilibreplace/'], CFLAGS=" -DNO_CONFIG_H")

env.libaff4_sources =  Split(uuid_files + source_files + " #lib/init.c " +
                             tdb_files + env.liboo_files) + env.libraptor_objs

## Autogenerate initialization functions for all classes
def generate_init_functions(target, source, env):
    class_initialisers = ''
    privates = set()
    module_inits = []
    for s in source:
        for line in open(s.path):
            m = re.search("CLASS\(([^,]+)", line)
            if m:
                privates.add(m.group(1))

            m = re.search("AFF4_MODULE_INIT\(([^\)]+)", line)
            if m:
                module_inits.append(m.group(1))
                continue

            m = re.search("VIRTUAL\(([^,]+)", line)
            if m and m.group(1) not in privates:
                class_initialisers += "%s_init((Object)&__%s);\n" % (m.group(1),m.group(1))

    module_inits.sort()
    data = """
/** This file is autogenerated by the build system.

We incorporate initialization functions for all plugins registered.
*/
#include "aff4_internal.h"

// initialiser prototypes
%(init_prototypes)s

void __attribute ((constructor)) _init_aff4_library (void) {
   init_aff4();
};

void init_aff4(void) {
%(class_initialisers)s

// Module Initialisers
%(init)s
};
""" % dict(init_prototypes = '\n'.join(['void %s_init();' % x for x in module_inits]),
           class_initialisers = class_initialisers,
           init = '\n'.join(['%s_init();' % x for x in module_inits]))

    fd = open(target[0].path,"w")
    fd.write(data)
    fd.close()

env.Command("#lib/init.c",
            Split(source_files),
            env.Action(generate_init_functions,
                       utils.warn("Generating init functions")))

env.libaff4_so = library = nenv.VersionedSharedLibrary("aff4", '0', env.libaff4_sources)

env.installed_libaff4_so = nenv.InstallVersionedSharedLibrary("${prefix}/lib/", library)
utils.install_target = nenv.Alias('install', '${prefix}')

env.libaff4_static_lib = nenv.StaticLibrary(
    'aff4',
    [env.libaff4_sources, env.libraptor_static_lib],
    )


PROCESSED_FILES = Split("""
              #include/aff4_utils.h
              #include/aff4_rdf.h
              #include/aff4_io.h
              #include/aff4_resolver.h
              #include/aff4_rdf_serialise.h
              #include/aff4_objects.h
              #include/aff4_crypto.h
              #include/aff4_errors.h
              #include/aff4_public.h
""")

VERBATIM_FILES = Split("""
              #include/aff4_public_misc.h
              #include/list.h
              #include/aff4_constants.h
""")

def build_aff4_objects_h(target, source, env):
    target = target[0]
    data = ''
    for s in source:
        data += open(s.get_abspath()).read()

    p = subprocess.Popen(["cpp", "-CC", "-DHEADERS_ONLY", "-imacros", "include/class.h", "-P"],
                         stdin = subprocess.PIPE, stdout = subprocess.PIPE)

    data = '\n'.join([x for x in data.splitlines() if '#include' not in x ])

    p.stdin.write(data)

    result = ''
    for s in VERBATIM_FILES:
        result += open(s[1:]).read()

    result += p.communicate()[0]

    try:
        p = subprocess.Popen("indent -linux -bbb -bad -cdb".split(),
                             stdin = subprocess.PIPE, stdout = subprocess.PIPE)

        p.stdin.write(result)
        result = p.communicate()[0]
    except OSError:
        utils.warn("Install indent to get a nicely formatted aff4.h file")

    fd = open(target.get_abspath(),'w')
    fd.write(result)
    fd.close()

target = '#include/aff4.h'
target = env.Command(target, PROCESSED_FILES, build_aff4_objects_h)
env.Depends(target, VERBATIM_FILES)
env.Install("${prefix}/include/", target)
